package com.workcell.workdata.xthings.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.workcell.workdata.common.core.constant.SecurityConstants;
import com.workcell.workdata.common.core.util.R;
import com.workcell.workdata.xthings.api.feign.RemoteIotOpenService;
import com.workcell.workdata.xthings.constant.CodeChainnumUseStateEnum;
import com.workcell.workdata.xthings.constant.HardwareTypeEnum;
import com.workcell.workdata.xthings.dto.CodeChainnumUpdateAliasVo;
import com.workcell.workdata.xthings.dto.CodeChainnumVo;
import com.workcell.workdata.xthings.dto.DataBaseAndRealTimeResult;
import com.workcell.workdata.xthings.entity.CodeChainnum;
import com.workcell.workdata.xthings.entity.CodeLevelGauge;
import com.workcell.workdata.xthings.entity.Manufacturer;
import com.workcell.workdata.xthings.entity.Tenant;
import com.workcell.workdata.xthings.mapper.CodeChainnumMapper;
import com.workcell.workdata.xthings.mapper.DeviceBindMapper;
import com.workcell.workdata.xthings.service.CodeChainnumService;
import com.workcell.workdata.xthings.service.CodeLevelGaugeService;
import com.workcell.workdata.xthings.service.ManufacturerService;
import com.workcell.workdata.xthings.service.TenantService;
import com.workcell.workdata.xthings.service.factory.CodeBaseFactory;
import com.workcell.workdata.xthings.utils.ShortUrlUtils;
import com.workcell.workdata.xthings.utils.SnowFlake;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.workcell.workdata.xthings.service.impl.DeviceBindServiceImpl.DEVICE_ADDRESS_MAX;
import static com.workcell.workdata.xthings.service.impl.DeviceBindServiceImpl.DEVICE_ADDRESS_MIN;

/**
 * 二维码码库
 *
 * @author 廖博文
 * @date 2022-11-30 09:34:00
 */
@Slf4j
@RequiredArgsConstructor
@Service
public class CodeChainnumServiceImpl extends ServiceImpl<CodeChainnumMapper, CodeChainnum> implements CodeChainnumService {

    @Value("${qrCode.shortChain}")
    private String shortChainUrl;
    private final CodeBaseFactory codeBaseFactory;

    private final RedisTemplate redisTemplate;

    private final CodeChainnumMapper codeChainnumMapper;

    private final RemoteIotOpenService remoteIotOpenService;

    private final ManufacturerService manufacturerService;

    private final TenantService tenantService;

    private final DeviceBindMapper deviceBindMapper;

    private final CodeLevelGaugeService codeLevelGaugeService;

    /**
     * 生成短链码
     *
     * @param saveChainNumVo
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean saveCodeChainNumInfo(CodeChainnumVo.SaveChainNumVo saveChainNumVo) throws Exception {
        // 获取基本信息
        String baseId = saveChainNumVo.getBaseId();
        String deviceNoParam = saveChainNumVo.getDeviceNo();
        String[] deviceNoList = deviceNoParam.split(",");
        String hardwareType = saveChainNumVo.getHardwareType();
        String addressId = saveChainNumVo.getAddressId();
        String alias = StrUtil.emptyToDefault(saveChainNumVo.getAlias(), saveChainNumVo.getHardwareName());
        String manufacturerCode = saveChainNumVo.getManufacturerCode();
        Manufacturer manufacturer = manufacturerService.selectByManufacturerCode(manufacturerCode);
        Assert.notNull(manufacturer, "厂商查询为空");
        String manufacturerFullName = manufacturer.getManufacturerFullName();
        String manufacturerShortName = manufacturer.getManufacturerShortName();
        String tenantCode = saveChainNumVo.getTenantCode();
        Tenant tenant = tenantService.selectByTenantCode(tenantCode);
        Assert.notNull(tenant, "租户查询为空");
        String tenantFullName = tenant.getTenantFullName();
        String tenantShortName = tenant.getTenantShortName();
        for (String deviceNo : deviceNoList) {
            // 校验该设备是否有存在短链
            LambdaQueryWrapper<CodeChainnum> codeChainnumCondition = new QueryWrapper<CodeChainnum>().lambda()
                    .eq(CodeChainnum::getBaseId, baseId).eq(CodeChainnum::getDeviceNo, deviceNo).eq(CodeChainnum::getHardwareType, hardwareType);
            CodeChainnum codeChainnum = this.getOne(codeChainnumCondition);
            if (ObjectUtil.isNotEmpty(codeChainnum)) {
                throw new Exception("设备号：" + deviceNo + "已存在短链");
            }
            // 短链生成
            String shotUrl = getShortUrl(baseId + deviceNo + hardwareType);
            // 添加二维码基本信息记录
            String codeID = SnowFlake.nextId() + "";
            CodeChainnum codeChainnumSaveBean = new CodeChainnum();
            codeChainnumSaveBean.setChainNum(shotUrl);
            codeChainnumSaveBean.setCodeId(codeID);
            codeChainnumSaveBean.setBaseId(baseId);
            codeChainnumSaveBean.setDeviceNo(deviceNo);
            codeChainnumSaveBean.setHardwareType(hardwareType);
            codeChainnumSaveBean.setHardwareName(saveChainNumVo.getHardwareName());
            codeChainnumSaveBean.setManufacturerCode(manufacturerCode);
            codeChainnumSaveBean.setManufacturerShortName(manufacturerShortName);
            codeChainnumSaveBean.setManufacturerFullName(manufacturerFullName);
            codeChainnumSaveBean.setTenantCode(tenantCode);
            codeChainnumSaveBean.setTenantShortName(tenantShortName);
            codeChainnumSaveBean.setTenantFullName(tenantFullName);
            if (HardwareTypeEnum.getByCode(hardwareType).getIsHasAddress() && StrUtil.isNotEmpty(addressId)) {
                Assert.isTrue(NumberUtil.isInteger(addressId), "机器码必须是一个范围{}-{}的整数", DEVICE_ADDRESS_MIN, DEVICE_ADDRESS_MAX);
                Assert.checkBetween(Integer.parseInt(addressId), DEVICE_ADDRESS_MIN, DEVICE_ADDRESS_MAX, "设备机器码不在范围{}-{}", DEVICE_ADDRESS_MIN, DEVICE_ADDRESS_MAX);
            }
            codeChainnumSaveBean.setAddressId(addressId);
            codeChainnumSaveBean.setAlias(alias);
            if (!this.save(codeChainnumSaveBean)) {
                throw new Exception("添加设备二维码信息失败");
            }
            // 添加二维码设备详情快照信息
            if (!codeBaseFactory.saveCodeBaseInfo(codeChainnumSaveBean, saveChainNumVo)) {
                throw new Exception("添加设备二维码快照失败");
            }
        }
        return true;
    }

    /**
     * 获取硬件列表清单
     *
     * @param type
     * @return
     */
    @Override
    public List<JSONObject> getHardewareList(String type) {
        Assert.isTrue(ObjectUtil.isNotEmpty(type), "硬件类型不能为空");
        return codeBaseFactory.getHardwareList(type);
    }

    /**
     * 模糊查询分页
     *
     * @param selectChainNumVo
     * @return
     */
    @Override
    public Page<CodeChainnum> getCodeChainnumVaguePage(CodeChainnumVo.SelectChainNumVo selectChainNumVo) {
        LambdaQueryWrapper<CodeChainnum> codeChainnumCondition = new QueryWrapper<CodeChainnum>().lambda()
                .and(ObjectUtil.isNotEmpty(selectChainNumVo.getKeyWord()), i -> i
                        .like(CodeChainnum::getDeviceNo, selectChainNumVo.getKeyWord())
                        .or()
                        .like(CodeChainnum::getHardwareName, selectChainNumVo.getKeyWord()))
                .eq(ObjectUtil.isNotEmpty(selectChainNumVo.getHardwareType()), CodeChainnum::getHardwareType, selectChainNumVo.getHardwareType())
                .like(ObjectUtil.isNotEmpty(selectChainNumVo.getAlias()), CodeChainnum::getAlias, selectChainNumVo.getAlias())
                .like(ObjectUtil.isNotEmpty(selectChainNumVo.getDeviceNo()), CodeChainnum::getDeviceNo, selectChainNumVo.getDeviceNo())
                .eq(ObjectUtil.isNotEmpty(selectChainNumVo.getUseState()), CodeChainnum::getUseState, selectChainNumVo.getUseState())
                .and(ObjectUtil.isNotEmpty(selectChainNumVo.getStartTime()) || ObjectUtil.isNotEmpty(selectChainNumVo.getEndTime())
                        , i -> i.between(CodeChainnum::getCreateTime, selectChainNumVo.getStartTime(), selectChainNumVo.getEndTime()))
                .eq(ObjectUtil.isNotEmpty(selectChainNumVo.getUseState()), CodeChainnum::getUseState, selectChainNumVo.getUseState())
                .orderByDesc(CodeChainnum::getCreateTime,CodeChainnum::getId);
        Page<CodeChainnum> page = new Page<>(selectChainNumVo.getPageNum(), selectChainNumVo.getPageSize());
        return this.page(page, codeChainnumCondition);
    }

    /**
     * 根据ID删除短链码信息
     *
     * @param id
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public R removeCodeChainNumById(String id) throws Exception {
        // 查询二维码短链信息
        CodeChainnum codeChainnum = this.getById(id);
        if (ObjectUtil.isEmpty(codeChainnum)) {
            return R.failed("二维码短链信息为空");
        }
        // 删除快照信息
        if (!codeBaseFactory.removeCodeById(codeChainnum.getCodeId(), codeChainnum.getHardwareType())) {
            return R.failed("删除快照信息失败");
        }
        // 删除二维码信息
        if (!this.removeById(id)) {
            throw new Exception("删除二维码信息失败");
        }

        // 删除设备时解除绑定关系
        deviceBindMapper.removeByDeviceNo(codeChainnum.getDeviceNo());

        //删除设备关联的TV
        redisTemplate.delete(String.format("XThingsTv_relatedSnAndDeviceId_%s", codeChainnum.getChainNum()));
        return R.ok("删除成功");
    }

    /**
     * 查询硬件快照信息
     *
     * @param codeId
     * @param type
     * @return
     */
    @Override
    public Object getHardwareInfo(String codeId, String type) {
        Assert.isTrue(ObjectUtil.isNotEmpty(codeId), "二维码ID不能为空");
        Assert.isTrue(ObjectUtil.isNotEmpty(type), "硬件类型不能为空");
        return codeBaseFactory.getHardwareInfo(codeId, type);
    }

    /**
     * 根据ID获取二维码
     *
     * @param ids
     * @returnß
     */
    @Override
    public List<JSONObject> getQrCodeById(List<String> ids) {
        Assert.isTrue(CollectionUtils.isNotEmpty(ids), "ID集合不能为空");
        List<JSONObject> jsonObjectList = new ArrayList<>(ids.size());
        for (String id : ids) {
            Assert.isTrue(ObjectUtil.isNotEmpty(id), "不能有空ID");
            CodeChainnum codeChainnum = this.getById(id);
            Assert.isTrue(ObjectUtil.isNotEmpty(codeChainnum), String.format("ID%s对象为空，请核实您的数据", id));
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("id", id);
            jsonObject.put("url", shortChainUrl + codeChainnum.getChainNum());
            jsonObject.put("hardwareName", codeChainnum.getHardwareName());
            jsonObjectList.add(jsonObject);
        }
        return jsonObjectList;
    }

    /**
     * 根据短链码获取二维码参数信息
     *
     * @param chainNum
     * @return
     */
    @Override
    public DataBaseAndRealTimeResult.DataChainNumResult getCodeInfoByChainNum(String chainNum) {
        Assert.isTrue(ObjectUtil.isNotEmpty(chainNum), "短链码不能为空");
        LambdaQueryWrapper<CodeChainnum> codeChainnumCondition = new QueryWrapper<CodeChainnum>().lambda()
                .eq(CodeChainnum::getChainNum, chainNum);
        CodeChainnum codeChainnum = this.getOne(codeChainnumCondition);
        Assert.isTrue(ObjectUtil.isNotEmpty(codeChainnum), "无效的短链码");
        DataBaseAndRealTimeResult.DataChainNumResult resultBean = new DataBaseAndRealTimeResult.DataChainNumResult();
        BeanUtil.copyProperties(codeChainnum, resultBean);
        // 硬件类型转义
        resultBean.setHardwareTypeMsg(HardwareTypeEnum.getByCode(resultBean.getHardwareType()).getMsg());
        return resultBean;
    }

    /**
     * 获取唯一短链
     *
     * @return
     */
    private String getShortUrl(String salt) {
        String shortUrl = ShortUrlUtils.shortUrl(salt);
        //校验是否存在
        LambdaQueryWrapper<CodeChainnum> codeChainnumCondition = new QueryWrapper<CodeChainnum>().lambda()
                .eq(CodeChainnum::getChainNum, salt);
        CodeChainnum codeChainnum = this.getOne(codeChainnumCondition);
        if (ObjectUtil.isNull(codeChainnum)) {
            return shortUrl;
        }
        log.info("短链获取到存在值：{}", shortUrl);
        return this.getShortUrl(salt + RandomUtil.randomNumbers(5));
    }

    /**
     * 根据时间段和类型查询总数
     *
     * @param hardwareType
     * @param startTime
     * @param endTime
     * @return
     */
    @Override
    public Integer getByTypeAndCreateTime(String hardwareType, LocalDateTime startTime, LocalDateTime endTime) {
        return this.baseMapper.getByTypeAndCreateTime(hardwareType, startTime, endTime);
    }

    @Override
    public Integer getByTypeAndUseState(String hardwareType, String useState) {
        return this.baseMapper.getByTypeAndUseState(hardwareType, useState);
    }

    @Override
    public CodeChainnum getByDeviceNoAndHardwareType(String deviceNo, String hardwareType) {
        Assert.notEmpty(deviceNo, "设备编号不能为空");
        // 设备是否存在判断
        LambdaQueryWrapper<CodeChainnum> lambdaQueryWrapper = Wrappers.lambdaQuery(CodeChainnum.class)
                .eq(CodeChainnum::getDeviceNo, deviceNo)
                .eq(CodeChainnum::getHardwareType, hardwareType);
        return this.getOne(lambdaQueryWrapper);
    }

    @Override
    public void updateUseState() {
        R result = remoteIotOpenService.getOnLineAllUserByInnerSvc(SecurityConstants.FROM_IN);
        if (result.getCode() == R.failed().getCode()) {
            log.info("XxlJob_updateTvUseState获取在线设备失败");
            return;
        }
        List<String> devices = (List<String>) result.getData();
        if (ObjectUtil.isEmpty(devices)) {
            log.info("XxlJob_updateTvUseState没有设备在线");
            return;
        }

        //online
        LambdaQueryWrapper<CodeChainnum> queryWrapper = Wrappers.lambdaQuery(CodeChainnum.class)
                .in(CodeChainnum::getChainNum, devices)
                .eq(CodeChainnum::getHardwareType, HardwareTypeEnum.TV_BOX.getCode())
                .and(codeChainnumLambdaQueryWrapper -> {
                    codeChainnumLambdaQueryWrapper
                            .eq(CodeChainnum::getUseState, CodeChainnumUseStateEnum.OFFLINE.getCode())
                            .or()
                            .eq(CodeChainnum::getUseState, CodeChainnumUseStateEnum.ACTIVATED.getCode());
                });
        List<String> idList = this.list(queryWrapper).stream().map(CodeChainnum::getId).collect(Collectors.toList());
        if (ObjectUtil.isNotEmpty(idList)) {
            LambdaUpdateWrapper<CodeChainnum> onlineUpdateWrapper = new LambdaUpdateWrapper<>();
            onlineUpdateWrapper.in(CodeChainnum::getId, idList);
            onlineUpdateWrapper.set(CodeChainnum::getUseState, CodeChainnumUseStateEnum.ONLINE.getCode());
            onlineUpdateWrapper.set(CodeChainnum::getLastOnlineTime, LocalDateTime.now());
            this.update(onlineUpdateWrapper);
        }

        //offline
        LambdaQueryWrapper<CodeChainnum> lambdaQueryWrapper = Wrappers.lambdaQuery(CodeChainnum.class)
                .notIn(CodeChainnum::getChainNum, devices)
                //.ne(CodeChainnum::getUseState,CodeChainnumUseStateEnum.INIT.getCode())
                .eq(CodeChainnum::getHardwareType, HardwareTypeEnum.TV_BOX.getCode())
                .and(codeChainnumLambdaQueryWrapper -> {
                    codeChainnumLambdaQueryWrapper
                            .eq(CodeChainnum::getUseState, CodeChainnumUseStateEnum.ONLINE.getCode())
                            .or()
                            .eq(CodeChainnum::getUseState, CodeChainnumUseStateEnum.ACTIVATED.getCode());
                });
        List<String> ids = this.list(lambdaQueryWrapper).stream().map(CodeChainnum::getId).collect(Collectors.toList());
        if (ObjectUtil.isEmpty(ids)) {
            return;
        }
        LambdaUpdateWrapper<CodeChainnum> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.in(CodeChainnum::getId, ids);
        updateWrapper.set(CodeChainnum::getUseState, CodeChainnumUseStateEnum.OFFLINE.getCode());
        this.update(updateWrapper);
    }

    @Override
    public Map<String, Object> deviceDetail(String deviceNo, String hardwareType) {
        Map<String, Object> map = codeChainnumMapper.deviceDetail(deviceNo, hardwareType);
        return MapUtil.isEmpty(map) ? MapUtil.empty() : MapUtil.toCamelCaseMap(map);
    }

    @Override
    public CodeChainnum getByChainnumAndHardwareType(String chainNum, String hardwareType) {
        Assert.notEmpty(chainNum, "设备编号SN不能为空");
        LambdaQueryWrapper<CodeChainnum> lambdaQueryWrapper = new QueryWrapper<CodeChainnum>().lambda()
                .eq(CodeChainnum::getChainNum, chainNum)
                .eq(CodeChainnum::getHardwareType, hardwareType);
        return this.getOne(lambdaQueryWrapper);
    }

    @Override
    public CodeChainnum getByDeviceNo(String deviceNo, String hardwareType) {
        Assert.notEmpty(deviceNo, "厂家设备编号不能为空");
        LambdaQueryWrapper<CodeChainnum> lambdaQueryWrapper = new QueryWrapper<CodeChainnum>().lambda()
                .eq(CodeChainnum::getDeviceNo, deviceNo)
                .eq(CodeChainnum::getHardwareType, hardwareType);
        return this.getOne(lambdaQueryWrapper);
    }

    @Override
    public boolean updateAliasByDeviceNo(CodeChainnumUpdateAliasVo codeChainnumUpdateAliasVo) {
        LambdaUpdateWrapper<CodeChainnum> lambdaUpdateWrapper = Wrappers.lambdaUpdate(CodeChainnum.class)
                .set(CodeChainnum::getAlias, codeChainnumUpdateAliasVo.getAlias())
                .eq(CodeChainnum::getDeviceNo, codeChainnumUpdateAliasVo.getDeviceNo());
        return codeChainnumMapper.update(null, lambdaUpdateWrapper) > 0;
    }

    @Override
    public CodeLevelGauge getCodeLevelGaugeInfo(String codeId) {
        Assert.isTrue(ObjectUtil.isNotEmpty(codeId), "二维码ID不能为空");
        return codeLevelGaugeService.getCodeLevelGaugeInfo(codeId);
    }



	@Override
	public Boolean updateHardwareStatus(String deviceNo, String manufacturerCode, String status,String hardwareType) {
		return codeBaseFactory.updateHardwareStatus(deviceNo,manufacturerCode,status,hardwareType);
	}
}
